

import { delay, EPlayerInputFrameType, IAfterFrames, IGameSyncFrame, IPlayerInputOperate } from "tsgf-sdk";

export interface InputHandler {
    [key: string]: ((playerId: string, inputFrame: IPlayerInputOperate, dt: number) => void);
}

export interface IFrameSyncConnect {
    /**设置事件: 收到服务端的一个同步帧时触发*/
    onSyncFrame: (syncFrame: IGameSyncFrame) => void;
    /**设置事件: 服务端要求发送状态同步数据时触发*/
    onRequireSyncState: () => void;
    /**客户端调用来发送本地的状态同步数据*/
    sendSyncState(stateData: object, stateFrameIndex: number): void;
}

/**帧同步执行器,对接帧同步的实现*/
export class FrameSyncExecutor {

    private static noneFrame: IGameSyncFrame = {
        frameIndex: 0,
        playerInputs: null,
    };

    inputHandler: any;
    operatesTypePropName: string;
    serverSyncFrameRate = 60;
    renderFrameInvMs = 1000 / this.serverSyncFrameRate;
    renderFrameDt = 1 / this.serverSyncFrameRate;

    /**本地的所有输入帧(不包含空帧) */
    allFrames: IGameSyncFrame[] = [];
    /**收到服务端的最大帧索引*/
    maxFrameIndex = -1;

    stateData: any = null;
    /**状态同步数据是在本帧索引的数据, 即追帧是从本帧索引+1开始*/
    stateFrameIndex = -1;
    /**当前执行到的帧索引(已执行)*/
    executeFrameIndex = -1;
    /**当前执行到的帧索引对应的数组索引(已执行)*/
    executeFrameIndexArrIndex = -1;
    /**执行帧已经停止*/
    executeFrameStop = true;
    executeNextFrameHandler: any;
    executeNextFrameTimerHD: any = 0;
    connect: IFrameSyncConnect;

    public onExecFrame: (dt: number, frameIndex: number) => void;
    public getSyncState?: () => any;

    /**
     * 
     * @date 2022/2/21 - 下午3:43:43
     *
     * @constructor
     * @param operatesTypePropName 输入帧操作里的区分字段名,如'operateType'
     * @param inputHandler 输入帧处理器,实现函数: 
     * //操作类型的玩家输入帧,单个操作触发
     * execInputOperates_<operatesTypePropName>(playerId: string, inputOperate: IPlayerInputOperate, dt: number, FrameIndex:number)
     * //非操作类型的玩家输入帧
     * execInputOthers(playerId: string, inputFrame: IPlayerInputFrame, dt: number, FrameIndex:number)
     * @param onExecFrame 一个输入帧执行完后触发(输入帧->玩家输入帧->输入帧操作) (PS:数据帧和渲染帧要区分开来)
     * @param getSyncState 当要求状态同步时,需要返回当前状态数据, 方便快速追帧, 不传表示禁用本功能
     */
    constructor(
        connect: IFrameSyncConnect,
        operatesTypePropName: string,
        inputHandler: any,
        onExecFrame: (dt: number, frameIndex: number) => void,
        getSyncState?: () => any) {
        this.connect = connect;
        this.operatesTypePropName = operatesTypePropName;
        this.inputHandler = inputHandler;
        this.onExecFrame = onExecFrame;
        this.getSyncState = getSyncState;
        this.executeNextFrameHandler = this.executeNextFrame.bind(this);

        this.connect.onSyncFrame = (frame) => {
            this.onSyncFrame(frame);
        };
        this.connect.onRequireSyncState = () => {
            if (this.getSyncState) {
                this.stateData = this.getSyncState();
                this.stateFrameIndex = this.executeFrameIndex;
                this.connect.sendSyncState(this.stateData, this.stateFrameIndex);
            }
        };
    }
    public async dispose(): Promise<void> {
        this.stopExecuteFrame();
    }

    /**收到服务端的一帧, 会推在帧队列里, 并做一些数据校验*/
    onSyncFrame(syncFrame: IGameSyncFrame) {
        //假设同步的帧是保证按顺序的!

        if (syncFrame.playerInputs) {
            //如果不是空帧,则加入帧数组
            this.allFrames.push(syncFrame);
        }

        //最大帧索引后推
        this.maxFrameIndex = syncFrame.frameIndex;

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    /**真正执行下一帧(按顺序触发多个输入操作的实现),最后触发逻辑帧事件,返回是否执行完所有帧了(可能是执行前就完了,也可能是执行后完了)*/
    executeOneFrame(dt: number): boolean {
        //要执行的帧索引
        const execFrameIndex = this.executeFrameIndex + 1;
        //帧索引超过服务器下发的最大帧索引,就返回true,表示停止执行了(执行完了)
        if (execFrameIndex > this.maxFrameIndex) return true;
        //更新当前执行帧索引
        this.executeFrameIndex = execFrameIndex;
        //假设本帧的数组索引(有可能不是,要取出来判断一下帧索引,不对那说明这一帧理应是空帧)
        const arrIndex = this.executeFrameIndexArrIndex + 1;
        let frame: IGameSyncFrame | undefined;
        if (arrIndex < this.allFrames.length) {
            //长度符合
            let tmpFrame = this.allFrames[arrIndex];
            if (tmpFrame && tmpFrame.frameIndex === execFrameIndex) {
                //击中,非空帧!
                frame = tmpFrame;
                //同时更新最后执行的数组索引
                this.executeFrameIndexArrIndex = arrIndex;
            }
        }
        //没匹配的,说明是空帧
        if (!frame) frame = FrameSyncExecutor.noneFrame;

        if (frame.playerInputs) {
            //非空帧
            for (let i = 0; i < frame.playerInputs.length; i++) {
                const inp = frame.playerInputs[i];
                if (inp.inputFrameType === EPlayerInputFrameType.Operates) {
                    if (inp.operates) {
                        for (let j = 0; j < inp.operates.length; j++) {
                            const op = inp.operates[j];
                            const fn = this.inputHandler['execInputOperates_' + op[this.operatesTypePropName]];
                            if (fn) fn.call(this.inputHandler, inp.playerId, op, dt, execFrameIndex);
                        }
                    }
                } else {
                    const fn = this.inputHandler['execInputOthers'];
                    if (fn) fn.call(this.inputHandler, inp.playerId, inp, dt, execFrameIndex);
                }
            }
        }

        this.onExecFrame?.call(this, dt, execFrameIndex);

        if (execFrameIndex >= this.maxFrameIndex) {
            return true;
        } else {
            return false;
        }
    }
    /**开始执行下一帧(或者追上落后帧),并且自动根据情况执行之后的帧,执行完则自动停下来,设置 executeFrameStop=true*/
    async executeNextFrame():Promise<void> {
        //处理帧信息
        if (this.executeOneFrame(this.renderFrameDt)) {
            //已经执行完所有帧了,等待服务器新消息
            this.executeFrameStop = true;
            return;
        }

        //未执行的帧数
        const unRenderFrameCount = this.maxFrameIndex - this.executeFrameIndex;
        //根据性能动态计算,下一帧要执行的间隔
        let frameInv = 0;
        if (unRenderFrameCount > 10) {
            //当缓存帧过多时,一次执行完
            for (let i = 0; i < unRenderFrameCount; i++) {
                if (this.executeOneFrame(this.renderFrameDt)) {
                    //已经执行完所有帧了,等待服务器新消息
                    this.executeFrameStop = true;
                    return;
                }
                //异步等待1ms,防止卡着处理会让
                await delay(1);
            }
        } else if (unRenderFrameCount > 3) {
            //相差一点,开始追帧(setTimetout 1,不卡死又能快速执行)
            frameInv = 1;
        } else {
            //正常速度
            frameInv = this.renderFrameInvMs;
        }
        if (this.executeFrameStop) return;

        /*
        //累计1k清理一波过期帧
        if (this.currFrameIndex > 1000) {
            this.frames.splice(0, this.currFrameIndex);
            this.currFrameIndex = 0;
        }
        */

        this.executeNextFrameTimerHD = setTimeout(
            this.executeNextFrameHandler,
            frameInv
        );
    }



    /**
     * 开始执行,可以传入追帧数据开始追帧执行,否则认为全新开始等待帧同步
     * @date 2022/5/21 - 00:10:18
     *
     * @param {number} frameRate
     * @param {?IAfterFrames} [afterFrames]
     */
    startExecuteFrame(frameRate: number, afterFrames?: IAfterFrames) {

        this.serverSyncFrameRate = frameRate;
        this.renderFrameInvMs = 1000 / this.serverSyncFrameRate;
        this.renderFrameDt = 1 / this.serverSyncFrameRate;

        if (afterFrames) {
            //有追帧
            this.allFrames = afterFrames.afterFrames;
            this.stateData = afterFrames.stateData;
            this.stateFrameIndex = afterFrames.stateFrameIndex;
            this.maxFrameIndex = afterFrames.afterEndFrameIndex;
            this.executeFrameIndex = this.stateFrameIndex;
            this.executeFrameIndexArrIndex = -1;//如果 afterFrames 就是从 stateFrameIndex+1 开始,则这里直接用-1接口
            if (this.executeFrameIndex > -1) {
                // 兼容 afterFrames 是全量帧数据的场景, 则需要找到当前帧索引, 对应的数组索引
                for (let i = 0; i < this.allFrames.length; i++) {
                    if (this.executeFrameIndex === this.allFrames[i].frameIndex) {
                        this.executeFrameIndexArrIndex = i - 1;//找到了, 设置为这个索引的前一个
                        break;
                    }
                }
            }
        } else {
            //重新开始
            this.allFrames = [];
            this.stateData = null;
            this.stateFrameIndex = -1;
            this.executeFrameIndex = -1;
            this.executeFrameIndexArrIndex = -1;
            this.maxFrameIndex = -1;
        }

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    stopExecuteFrame() {
        this.executeFrameStop = true;
        clearTimeout(this.executeNextFrameTimerHD);
    }
}